﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization;
using System.Drawing.Drawing2D;

namespace PathFinder
{
    [Serializable()]
    public class LineProperties
    {

        public enum Directions
        {
            [XmlEnum(Name = "0")]
            Plain = 0,
            [XmlEnum(Name = "1")]
            AtoB = 1,
            [XmlEnum(Name = "2")]
            BtoA = 2
        }

        public enum Qualities
        {
            [XmlEnum(Name = "0")]
            Excellent = 0,
            [XmlEnum(Name = "1")]
            Good = 1,
            [XmlEnum(Name = "2")]
            Average = 2,
            [XmlEnum(Name = "3")]
            Sufficient = 3,
            [XmlEnum(Name = "4")]
            Horrible = 4
        }

        String name;
        public String Name { get { return name; } set { name = value; } }

        Directions direction;
        public Directions Direction { get { return direction; } set { direction = value; } }

        int directionIndex;
        public int DirectionIndex { get { return directionIndex; } set { directionIndex = value; } }

        bool isRequired;
        public bool IsRequired { get { return isRequired; } set { isRequired = value; } }

        Qualities quality;
        public Qualities Quality { get { return quality; } set { quality = value; } }
        
        public LineProperties()
        {
            name = "default";
            direction = Directions.Plain;
            directionIndex = 0;
            isRequired = true;
            quality = Qualities.Excellent;
        }

        public LineProperties(LineProperties props)
        {
            this.direction = props.direction;
            this.directionIndex = props.directionIndex;
            this.isRequired = props.isRequired;
            this.name = props.name;
            this.quality = props.quality;
        }

        public void update(String name, Directions slope, int directionIndex, bool isRequired, Qualities feasibility)
        {
            this.name = name;
            this.direction = slope;
            this.directionIndex = directionIndex;
            this.isRequired = isRequired;
            this.quality = feasibility;
        }

        public void Validate()
        {
            if ((int)direction > 3 || (int)direction < 0 || directionIndex < 0 || directionIndex > 10 || (int)quality < 0 || (int)quality > 4)
                throw new FormatException();
        }
    }
    [Serializable()]
    public class GraphLine
    {

        #region class definitions
        [XmlIgnore]
        public static Pen RequiredPen = new Pen(new SolidBrush(Color.Blue), 4);
        [XmlIgnore]
        public static Pen NonRequiredPen = new Pen(new SolidBrush(Color.Cyan), 4);
        [XmlIgnore]
        static Pen DeletingPen = new Pen(new SolidBrush(Color.Red), 4);
        [XmlIgnore]
        static Pen SelectedPen = new Pen(new SolidBrush(Color.Orange), 4);
        [XmlIgnore]
        static Pen PropertiesPen = new Pen(new SolidBrush(Color.Yellow), 4);
        [XmlIgnore]
        static Pen BlackPen = new Pen(new SolidBrush(Color.Black), 6);
        public GraphPoint a;
        public GraphPoint b;
        public LineProperties properties;

        [XmlIgnore]
        public List<GraphLine> neighboringLines;
        [XmlIgnore]
        public int WalkedTimes = 0;
        [XmlIgnore]
        public int WalkedCapacity = 1;
        public GraphLine() { }
        public GraphLine(GraphPoint A, GraphPoint B,  LineProperties props = null)
        {
            a = A;
            b = B;
            if (props != null)
            {
                properties = new LineProperties(props);
            }
            else
            {
                properties = new LineProperties();
            }
        }

        #endregion

        #region draw functions

        public void Draw(Graphics g)
        {
            if (properties.IsRequired)
            {
                g.DrawLine(RequiredPen, a.point, b.point);
            }
            else
            {
                g.DrawLine(NonRequiredPen, a.point, b.point);
            }
                a.Draw(g);
                b.Draw(g);
        }

        public void DrawExport(Graphics g)
        {
            if (properties.IsRequired)
            {
                g.DrawLine(RequiredPen, a.point, b.point);
            }
            else
            {
                g.DrawLine(NonRequiredPen, a.point, b.point);
            }
        }
        public void DrawBackground(Graphics g)
        {
            g.DrawLine(BlackPen, a.point, b.point);
            a.DrawBackground(g);
            b.DrawBackground(g);
        }
        public void DrawDeleting(Graphics g)
        {
            g.DrawLine(DeletingPen, a.point, b.point);
            a.Draw(g);
            b.Draw(g);
        }
        public void DrawSelected(Graphics g)
        {
            g.DrawLine(SelectedPen, a.point, b.point);
            a.Draw(g);
            b.Draw(g);
        }
        public void DrawProperties(Graphics g)
        {
            g.DrawLine(PropertiesPen, a.point, b.point);
            a.Draw(g);
            b.Draw(g);
        }

        #endregion

        public Point IsAtTheLine(Point p)
        {
            int maxX, maxY, minX, minY;
            if (a.point.X > b.point.X)
            {
                maxX = a.point.X;
                minX = b.point.X;
            }
            else
            {
                maxX = b.point.X;
                minX = a.point.X;
            }
            if (a.point.Y > b.point.Y)
            {
                maxY = a.point.Y;
                minY = b.point.Y;
            }
            else
            {
                maxY = b.point.Y;
                minY = a.point.Y;
            }
            if (p.X < minX - 10 || p.X > maxX + 10 || p.Y < minY - 10 || p.Y > maxY + 10)
                return new Point(-1, -1);
            //ax + by +c = 0
            //hranice grafu
            int smerX = a.point.X - b.point.X;
            int smerY = a.point.Y - b.point.Y;
            double A = -smerY;
            double B = smerX;
            double C = -(A * a.point.X + B * a.point.Y);

            //kolmice na hranici z bodu p
            //-bx + ay + d = 0
            double X, Y;
            if (Math.Abs(smerX) < Math.Abs(smerY))
            {
                double D = -(smerX * p.X + smerY * p.Y);
                double Z = B * p.X - A * p.Y;
                if (A != 0)
                {
                    Y = (-B * C + D * A) / (A * A + B * B);
                    X = -(B * Y + C) / A;
                }
                else
                {
                    Y = -C / B;
                    X = p.X;
                }
            }
            else
            {
                double D = B * p.X - A * p.Y;
                if (B != 0)
                {
                    X = (-A * C + B * D) / (B * B + A * A);
                    Y = -(A * X + C) / B;
                }
                else
                {
                    Y = p.Y;
                    if (A != 0)
                    {
                        X = -C / A;
                    }
                    else
                    {
                        X = p.Y;
                    }
                }
            }
            double distance = (int)((X - p.X) * (X - p.X) + (Y - p.Y) * (Y - p.Y));
            if (distance <= 50)
            {
                if (X < minX)
                    X = minX;
                if (X > maxX)
                    X = maxX;
                if (Y < minY)
                    Y = minY;
                if (Y > maxY)
                    Y = maxY;
                return new Point((int)X,(int) Y);
            }
            return new Point(-1, -1);
        }

        public bool HasSamePoints(GraphLine line)
        {
            if (this == line)
                return false;
            if (a == line.a && b == line.b)
                return true;
            if (a == line.b && b == line.a)
                return true;
            return false;
        }

        public bool HasGraphPoint(GraphPoint pt)
        {
            return (pt == a || pt == b);
        }

        public double Length()
        {
           return Math.Pow(Math.Pow(a.point.X - b.point.X, 2) + Math.Pow(a.point.Y - b.point.Y, 2), 0.5)/10;
           
        }

        public GraphPoint GetSecondPoint(GraphPoint first)
        {
                if (first == a)
                {
                    return b;
                }
                else if (first == b)
                {
                    return a;
                }
                else
                {
                    return null;
                }
        }

        public double Difficulty(GraphPoint start)
        {
            if (!HasGraphPoint(start))
                return Double.NaN;
            double strengthPercent = (double)(properties.DirectionIndex+1) / 10;
            double value = Length() * ((int)properties.Quality+1);
            if (properties.Direction == LineProperties.Directions.Plain)
            {
                return value;
            }
            else if (properties.Direction == LineProperties.Directions.AtoB)
            {
                if (start == a)
                {
                    return value * strengthPercent;
                }
                else
                {
                    return value / strengthPercent;
                }
            }
            else
            {
                if (start == b)
                {
                    return value * strengthPercent;
                }
                else
                {
                    return value / strengthPercent;
                }
            }
        }

        public void Validate(Size WindowSize)
        {
            a.Validate(WindowSize);
            b.Validate(WindowSize);
            properties.Validate();
        }
    }
}
